﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace iNet.Media.Imaging.Exif
{
	/// <summary>
	/// TIFF 讀取器。
	/// </summary>
	public class TiffReader : IExifTagReader
	{
		#region ImageDataInfo
		class ImageDataInfo
		{
			public readonly Ifd Ifd;
			public byte[][] ImageData;
			public uint[] StripLengths;
			public uint[] StripOffsets;

			public ImageDataInfo(Ifd ifd)
			{
				this.Ifd = ifd;
			}
		}
		#endregion
		#region 靜態欄位
		static readonly Regex CanonManufacturerRegex = new Regex(@"(^|[\s]+)canon($|[\s]+)", RegexOptions.IgnoreCase);
		static readonly Regex NikonManufacturerRegex = new Regex(@"(^|[\s]+)nikon($|[\s]+)", RegexOptions.IgnoreCase);
		static readonly Regex NikonModelRegex = new Regex(@"(^|[\s]+)(nikon)?[\s]*(?<Model>.*)$", RegexOptions.IgnoreCase);
		static readonly Regex PentaxManufacturerRegex = new Regex(@"(^|[\s]+)pentax($|[\s]+)", RegexOptions.IgnoreCase);
		#endregion
		#region IFD 位移欄位
		long? _ExifIfdOffset;
		long? _GpsIfdOffset;
		long? _InteropIfdOffset;
		long? _MakerNoteOffset;
		long? _SubIfd0Offset;
		long? _SubIfd1Offset;
		#endregion
		#region 欄位
		BitConverter _BitConverter;
		string _CameraManufacturer;
		string _CameraModel = "";
		Ifd? _CustomIfd;
		bool _HasError;
		Ifd? _Ifd;
		readonly Dictionary<Ifd, ImageDataInfo> _ImageData = new Dictionary<Ifd, ImageDataInfo>();
		Ifd? _MakerNoteIfd;
		readonly long _ReferenceStreamPosition;
		readonly Stream _Stream;
		ExifTag _Tag;
		IExifTagReader _TagReader;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 TiffReader 執行個體。
		/// </summary>
		/// <param name="stream">提供 TIFF 標頭資料的資料流。</param>
		/// <param name="customIfd">指定的 IFD，Null (Visual Basic 為 Nothing) 表示根據 TIFF 標頭內容決定 IFD。</param>
		/// <exception cref="ArgumentNullException">stream 為 Null (Visual Basic 為 Nothing)。</exception>
		public TiffReader(Stream stream, Ifd? customIfd = null)
		{
			//確認參數
			if (stream == null)
				throw new ArgumentNullException("stream");

			//初始化
			_Stream = stream;
			_CustomIfd = customIfd;

			//取得 BitConverter
			byte[] buffer = new byte[4];
			try
			{
				_ReferenceStreamPosition = stream.Position;
				if (stream.Read(buffer, 0, 4) == 4)
				{
					//取得 BitConverter
					if (buffer[0] == 'I' && buffer[1] == 'I')
						_BitConverter = BitConverter.LittleEndian;
					else if (buffer[0] == 'M' && buffer[1] == 'M')
						_BitConverter = BitConverter.BigEndian;
					else
						_HasError = true;
					
					//確認識別碼
					if (_BitConverter != null && _BitConverter.ToUInt16(buffer, 2) != this.HeaderSignature)
						_HasError = true;

					//移動至 IFD0
					if (_BitConverter != null && stream.Read(buffer, 0, 4) == 4)
					{
						long offset = _BitConverter.ToUInt32(buffer);
						if (offset != 8)
							stream.Seek(offset - 8, SeekOrigin.Current);
					}
					else
						_HasError = true;
				}
				else
					_HasError = true;
			}
			catch
			{
				_HasError = true;
			}
		}
		#endregion
		#region BitConverter
		/// <summary>
		/// 取得提供位元組轉換的 BitConverter。
		/// </summary>
		public BitConverter BitConverter
		{
			get
			{
				return _BitConverter;
			}
		}
		#endregion
		#region HasError
		/// <summary>
		/// 取得值以確認讀取期間是否發生錯誤。
		/// </summary>
		public bool HasError
		{
			get
			{
				return _HasError;
			}
		}
		#endregion
		#region HeaderSignature
		/// <summary>
		/// 取得 TIFF 標頭識別碼。
		/// </summary>
		protected virtual ushort HeaderSignature
		{
			get
			{
				return 0x2a;
			}
		}
		#endregion
		#region IFD
		/// <summary>
		/// 取得正在讀取的 IFD。
		/// </summary>
		public Ifd? Ifd
		{
			get
			{
				if (_CustomIfd != null)
					return _CustomIfd.Value;
				return _Ifd;
			}
		}
		#endregion
		#region ParseMakerNoteTag
		void ParseMakerNoteTag()
		{
			if (string.IsNullOrEmpty(_CameraManufacturer))
				return;
			if (CanonManufacturerRegex.IsMatch(_CameraManufacturer))
				_MakerNoteIfd = Exif.Ifd.CanonMakerNote;
			else if (NikonManufacturerRegex.IsMatch(_CameraManufacturer))
				this.ParseNikonMakerNoteTag();
		}
		#endregion
		#region ParseNikonMakerNoteTag
		void ParseNikonMakerNoteTag()
		{
			Match match = NikonModelRegex.Match(_CameraModel);
			if (match.Success)
			{
				switch (match.Groups["Model"].Value.ToUpper())
				{
					case "E700":
					case "E800":
					case "E900":
					case "E900S":
					case "E910":
					case "E950":
						_MakerNoteIfd = Exif.Ifd.NikonMakerNoteType1;
						break;
					case "D1":
					case "E990":
						_MakerNoteIfd = Exif.Ifd.NikonMakerNoteType2;
						break;
					default:
						_MakerNoteIfd = Exif.Ifd.NikonMakerNoteType3;
						break;
				}
			}
			else
				_MakerNoteIfd = Exif.Ifd.NikonMakerNoteType3;
		}
		#endregion
		#region ParseTag
		bool ParseTag()
		{
			if (_Ifd == Exif.Ifd.Ifd0)
			{
				if (_TagReader.TagId == 330 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag())					// Sub IFD (NEF)
				{
					if (_Tag != null && _Tag.Count == 2 && _Tag.Data != null && _Tag.Data.Length == 8)
					{
						_SubIfd0Offset = _BitConverter.ToUInt32(_Tag.Data, 0);
						_SubIfd1Offset = _BitConverter.ToUInt32(_Tag.Data, 4);
						return true;
					}
				}
				else if (_TagReader.TagId == 0x010f && _TagReader.TagType == ExifTagType.AsciiString && this.ReadTag())		// 相機廠商
				{
					if (_Tag != null && _Tag.Data != null)
						_CameraManufacturer = Encoding.Default.GetString(_Tag.Data).TrimEnd('\0').Trim();
				}
				else if (_TagReader.TagId == 0x0110 && _TagReader.TagType == ExifTagType.AsciiString && this.ReadTag())		// 相機型號
				{
					if (_Tag != null && _Tag.Data != null)
						_CameraModel = Encoding.Default.GetString(_Tag.Data).TrimEnd('\0').Trim();
				}
				else if (_TagReader.TagId == 273 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag() && _Tag != null)	// StripOffsets
				{
					ImageDataInfo imageData;
					if (!_ImageData.TryGetValue(Exif.Ifd.Ifd0, out imageData))
					{
						imageData = new ImageDataInfo(Exif.Ifd.Ifd0);
						_ImageData[Exif.Ifd.Ifd0] = imageData;
					}
					imageData.StripOffsets = (_Tag.ExtractValue() as uint[]);
				}
				else if (_TagReader.TagId == 279 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag() && _Tag != null)	// StripByteCounts
				{
					ImageDataInfo imageData;
					if (!_ImageData.TryGetValue(Exif.Ifd.Ifd0, out imageData))
					{
						imageData = new ImageDataInfo(Exif.Ifd.Ifd0);
						_ImageData[Exif.Ifd.Ifd0] = imageData;
					}
					imageData.StripLengths = (_Tag.ExtractValue() as uint[]);
				}
			}
			else if (_Ifd == Exif.Ifd.Ifd1 || _Ifd == Exif.Ifd.Ifd2 || _Ifd == Exif.Ifd.Ifd3)
			{
				if (_TagReader.TagId == 273 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag() && _Tag != null)		// StripOffsets
				{
					ImageDataInfo imageData;
					if (!_ImageData.TryGetValue(_Ifd.Value, out imageData))
					{
						imageData = new ImageDataInfo(_Ifd.Value);
						_ImageData[_Ifd.Value] = imageData;
					}
					imageData.StripOffsets = (_Tag.ExtractValue() as uint[]);
				}
				else if (_TagReader.TagId == 279 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag() && _Tag != null)	// StripByteCounts
				{
					ImageDataInfo imageData;
					if (!_ImageData.TryGetValue(_Ifd.Value, out imageData))
					{
						imageData = new ImageDataInfo(_Ifd.Value);
						_ImageData[_Ifd.Value] = imageData;
					}
					imageData.StripLengths = (_Tag.ExtractValue() as uint[]);
				}
			}
			if (_TagReader.TagId == 34665 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag())					// Exif IFD
			{
				if (_Tag != null && _Tag.Data != null && _Tag.Data.Length >= 4)
				{
					_ExifIfdOffset = _BitConverter.ToUInt32(_Tag.Data);
					return true;
				}
			}
			else if (_TagReader.TagId == 34853 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag())				// GPS IFD
			{
				if (_Tag != null && _Tag.Data != null && _Tag.Data.Length >= 4)
				{
					_GpsIfdOffset = _BitConverter.ToUInt32(_Tag.Data);
					return true;
				}
			}
			else if (_TagReader.TagId == 40965 && _TagReader.TagType == ExifTagType.UInt32 && this.ReadTag())				// Interop IFD
			{
				if (_Tag != null && _Tag.Data != null && _Tag.Data.Length >= 4)
				{
					_InteropIfdOffset = _BitConverter.ToUInt32(_Tag.Data);
					return true;
				}
			}
			else if (_Ifd == Exif.Ifd.ExifIfd)
			{
				if (_TagReader.TagId == 0x927c && this.ReadTag())															// MakerNote
				{
					if (_Tag != null && _Tag.Data != null && _Tag.Data.Length == 4)
					{
						_MakerNoteOffset = _BitConverter.ToUInt32(_Tag.Data);
						this.ParseMakerNoteTag();
						return true;
					}
				}
			}
			return false;
		}
		#endregion
		#region ReadAllTags
		/// <summary>
		/// 讀取所有的 EXIF Tag。
		/// </summary>
		/// <returns>所有的 EXIF Tag。</returns>
		public ExifTag[] ReadAllTags()
		{
			List<ExifTag> list = new List<ExifTag>();
			while (this.ReadNextTag())
			{
				ExifTag tag = this.Tag;
				if (tag != null)
					list.Add(tag);
			}
			return list.ToArray();
		}
        #endregion
		#region ReadImageData
		/// <summary>
		/// 讀取指定 IFD 所描述的影像資料。
		/// </summary>
		/// <param name="ifd">指定的 IFD。</param>
		/// <returns>指定 IFD 所描述的影像資料，Null (Visual Basic 為 Nothing) 表示影像資料不存在 -或- 尚未讀取完整的影像資訊。</returns>
		public byte[][] ReadImageData(Exif.Ifd ifd)
		{
			//確認狀態
			if (_HasError)
				return null;

			//取得影像資料
			ImageDataInfo imageData;
			if (!_ImageData.TryGetValue(ifd, out imageData))
				return null;
			if (imageData.ImageData != null)
				return imageData.ImageData;
			if (imageData.StripLengths == null || imageData.StripOffsets == null || imageData.StripOffsets.Length != imageData.StripLengths.Length)
				return null;
			
			//儲存資料流位置
			long streamPosition;
			try
			{
				streamPosition = _Stream.Position;
			}
			catch
			{
				_HasError = true;
				return null;
			}

			//讀取影像資料
			try
			{
				int stripCount = imageData.StripOffsets.Length;
				byte[][] data = new byte[stripCount][];
				for (int i = 0; i < stripCount; ++i)
				{
					int stripLength = (int)imageData.StripLengths[i];
					_Stream.Position = (_ReferenceStreamPosition + imageData.StripOffsets[i]);
					data[i] = new byte[stripLength];
					if (_Stream.Read(data[i], 0, stripLength) != stripLength)
						throw new ArgumentException();
				}
				imageData.ImageData = data;
			}
			catch
			{
				_HasError = true;
			}
			finally
			{
				try
				{
					_Stream.Position = streamPosition;
				}
				catch
				{
					_HasError = true;
				}
			}
			return imageData.ImageData;
		}
		#endregion
		#region ReadNextTag
		/// <summary>
		/// 讀取 IFD 中的下一個 EXIF Tag。
		/// </summary>
		/// <returns>True 表示讀取成功，False 表示已讀取至最後一個 EXIF Tag -或- 讀取時發生錯誤。</returns>
		public bool ReadNextTag()
		{
			//確認狀態
			if (_HasError)
				return false;

			//讀取 IFD0
			if (_TagReader == null)
			{
				_Ifd = (_CustomIfd == null ? Exif.Ifd.Ifd0 : _CustomIfd.Value);
				_TagReader = new IfdReader(_Stream, _ReferenceStreamPosition, _Ifd.Value, _BitConverter);
				if (_TagReader.HasError)
				{
					_HasError = true;
					_Ifd = null;
					_Tag = null;
					_TagReader = null;
					return false;
				}
			}

			//讀取下一個 Tag
			while (true)
			{
				//讀取下一個 Tag
				_Tag = null;
				if (_TagReader.ReadNextTag())
				{
					try
					{
						if (this.ParseTag())
							continue;
						return true;
					}
					catch
					{
						_HasError = true;
					}
				}

				//確認錯誤
				if (_TagReader.HasError || _HasError)
				{
					_HasError = true;
					_Ifd = null;
					_TagReader = null;
					return false;
				}

				//移動至下一個 IFD
				try
				{
					//移動至下一個 IFD
					if (_CustomIfd == null)
					{
						switch (_Ifd)
						{
							case Exif.Ifd.Ifd0:
								{
									long? offset = (_TagReader as IfdReader).NextIfdOffset;
									if (offset == null || offset == 0)
										goto case Exif.Ifd.Ifd1;
									_Stream.Position = (_ReferenceStreamPosition + offset.Value);
									_Ifd = Exif.Ifd.Ifd1;
								}
								break;
							case Exif.Ifd.Ifd1:
								{
									long? offset = (_TagReader as IfdReader).NextIfdOffset;
									if (offset == null || offset == 0)
										goto case Exif.Ifd.Ifd2;
									_Stream.Position = (_ReferenceStreamPosition + offset.Value);
									_Ifd = Exif.Ifd.Ifd2;
								}
								break;
							case Exif.Ifd.Ifd2:
								{
									long? offset = (_TagReader as IfdReader).NextIfdOffset;
									if (offset == null || offset == 0)
										goto case Exif.Ifd.Ifd3;
									_Stream.Position = (_ReferenceStreamPosition + offset.Value);
									_Ifd = Exif.Ifd.Ifd3;
								}
								break;
							case Exif.Ifd.Ifd3:
								if (_ExifIfdOffset == null)
									goto case Exif.Ifd.ExifIfd;
								_Stream.Position = (_ReferenceStreamPosition + _ExifIfdOffset.Value);
								_Ifd = Exif.Ifd.ExifIfd;
								break;
							case Exif.Ifd.ExifIfd:
								if (_SubIfd0Offset == null)
									goto case Exif.Ifd.SubIfd0;
								_Stream.Position = (_ReferenceStreamPosition + _SubIfd0Offset.Value);
								_Ifd = Exif.Ifd.SubIfd0;
								break;
							case Exif.Ifd.SubIfd0:
								if (_SubIfd1Offset == null)
									goto case Exif.Ifd.SubIfd1;
								_Stream.Position = (_ReferenceStreamPosition + _SubIfd1Offset.Value);
								_Ifd = Exif.Ifd.SubIfd1;
								break;
							case Exif.Ifd.SubIfd1:
								if (_GpsIfdOffset == null)
									goto case Exif.Ifd.GpsIfd;
								_Stream.Position = (_ReferenceStreamPosition + _GpsIfdOffset.Value);
								_Ifd = Exif.Ifd.GpsIfd;
								break;
							case Exif.Ifd.GpsIfd:
								if (_InteropIfdOffset == null)
									goto case Exif.Ifd.InteroperabilityIfd;
								_Stream.Position = (_ReferenceStreamPosition + _InteropIfdOffset.Value);
								_Ifd = Exif.Ifd.InteroperabilityIfd;
								break;
							case Exif.Ifd.InteroperabilityIfd:
								if (_MakerNoteOffset == null || _MakerNoteIfd == null)
									goto default;
								_Stream.Position = (_ReferenceStreamPosition + _MakerNoteOffset.Value);
								_Ifd = _MakerNoteIfd;
								break;
							default:
								return false;
						}
					}
					else if (_TagReader is IfdReader)
					{
						long? offset = (_TagReader as IfdReader).NextIfdOffset;
						if (offset == null || offset == 0)
							return false;
						_Stream.Position = (_ReferenceStreamPosition + offset.Value);
					}
					else
						return false;

					//建立新的 EXIF Tag Reader
					switch (_Ifd.Value)
					{
						case Exif.Ifd.NikonMakerNoteType1:
							_Stream.Seek(8, SeekOrigin.Current);
							goto default;
						case Exif.Ifd.NikonMakerNoteType3:
							_Stream.Seek(10, SeekOrigin.Current);
							_TagReader = new TiffReader(_Stream, _Ifd);
							break;
						default:
							_TagReader = new IfdReader(_Stream, _ReferenceStreamPosition, _Ifd.Value, _BitConverter);
							break;
					}
				}
				catch
				{
					_HasError = true;
					return false;
				}
			}
		}
		#endregion
		#region ReadTag
		bool ReadTag()
		{
			if (!_HasError && _TagReader != null)
			{
				_Tag = _TagReader.Tag;
				return (_Tag != null);
			}
			else
			{
				_Tag = null;
				return false;
			}
		}
        #endregion
		#region Tag
		/// <summary>
		/// 取得目前讀取的 EXIF Tag。
		/// </summary>
		public ExifTag Tag
		{
			get
			{
				if (_HasError || _TagReader == null)
					return null;
				if (_Tag == null)
					this.ReadTag();
				return _Tag;
			}
		}
		#endregion
		#region TagId
		/// <summary>
		/// 取得目前讀取的 EXIF Tag ID。
		/// </summary>
		public ushort? TagId
		{
			get
			{
				return (_TagReader != null ? _TagReader.TagId : null);
			}
		}
		#endregion
		#region TagType
		/// <summary>
		/// 取得目前讀取的 EXIF Tag 資料類型。
		/// </summary>
		public ExifTagType? TagType
		{
			get 
			{
				return (_TagReader != null ? _TagReader.TagType : null);
			}
		}
		#endregion
	}
}
